﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Practices.Unity;
using TvdP;
using System.Runtime.CompilerServices;
using Microsoft.Practices.Unity.ObjectBuilder;
using Microsoft.Practices.ObjectBuilder2;

namespace TvdP
{
    /// <summary>
    /// Sticks an IBehaviors object to a UnityContainer as a PostInitialization strategy. It will apply the applicable behaviors to the built up object.
    /// The UnityContainer itself or its applicable child will be passed as context to the initialization methods.
    /// </summary>
    public class BehaviorsContainerExtension : UnityContainerExtension
    {
        public BehaviorsContainerExtension(IBehaviors behaviors)
        {
            if (behaviors == null)
                throw new ArgumentNullException("behaviors");

            _Behaviors = behaviors;
        }

        IBehaviors _Behaviors;

        class BehaviorStrategy : Microsoft.Practices.ObjectBuilder2.BuilderStrategy
        {
            public ConditionalWeakTable<object, BehaviorStrategy> _IsInitalizedByRegistry;
            public IBehaviors _Behaviors;
            public IUnityContainer _Context;

            public override void PostBuildUp(IBuilderContext context)
            {
                base.PostBuildUp(context);

                var existing = context.Existing;

                if (
                    existing != null
                    && !existing.GetType().IsValueType
                )
                {
                    //Strategies registerd with child containers get called first. They register themselves as 'already initialized token'
                    //so that strategies registered with parent containers do not initialize the object again and with the wrong context.
                    var initializer = _IsInitalizedByRegistry.GetValue(existing, _ => this);

                    if(object.ReferenceEquals(this, initializer))
                        _Behaviors.Apply(context.Existing, _Context);
                }
            }
        }

        protected override void Initialize()
        {
            SetUp(this.Context, new ConditionalWeakTable<object, BehaviorStrategy>());
        }

        void SetUp(ExtensionContext context, ConditionalWeakTable<object, BehaviorStrategy> initializedByRegistry)
        {
            //every child gets its own version of the BehaviorStrategy so that the behavior initializers get called with the child as context. 
            context.ChildContainerCreated += 
                (s, e) => SetUp(e.ChildContext, initializedByRegistry)
            ;

            context.Strategies.Add(
                new BehaviorStrategy 
                { 
                    _Behaviors = _Behaviors, 
                    _Context = context.Container, 
                    _IsInitalizedByRegistry = initializedByRegistry 
                }, 
                UnityBuildStage.PostInitialization
            );
        }
    }
}
